﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using Cofe.Core;
using Cofe.Core.Entry;
using Cofe.Core.Implements;
using Cofe.Core.Property;
using Cofe.Core.Utils;
using Cofe.Core.Validater;
using Cofe.Data;
using Cofe.Web.Utils;

namespace Cofe.Web.Controllers.Api
{
    [Authorize]
    public class EntryController : ApiController
    {

        #region Helper methods

        private Uri findRedirect(IPropertyHost ph, string rel)
        {
            foreach (var l in UrlUtils.CreateLinks(ph))
                if (l.RelationTypeStr.Equals(rel, StringComparison.CurrentCultureIgnoreCase))
                {
                    return l.Uri;
                }
            return null;
        }

        private HttpResponseMessage handleRedirect(ICofeSystemInfo entry, string rel)
        {
            var newUrl = findRedirect(entry.Properties, rel);
            if (newUrl != null)
            {
                newUrl = new Uri(newUrl.AbsoluteUri + HttpContext.Current.Request.QueryString
                    .ToQueryString(key => !key.Equals("rel", StringComparison.InvariantCultureIgnoreCase)));

                var response = Request.CreateResponse(HttpStatusCode.Redirect);
                response.Headers.Location = newUrl;
                return response;
            }
            else return Request.CreateResponse(HttpStatusCode.BadRequest);
        }

        private DateTime? getLastModifiedTime(CofeSystemInfo entry, bool isListing)
        {
            DateTime? lastModifiedUtc = null;
            if (isListing && entry.Properties.Behaviors.IsPropertySupported(CacheProperties.LastListTimeUtc))
                lastModifiedUtc = entry.Properties.Behaviors.GetProperty<DateTime>(CacheProperties.LastListTimeUtc);
            else
                if (entry.Properties.Behaviors.IsPropertySupported(CacheProperties.LastCachedTimeUtc))
                    lastModifiedUtc = entry.Properties.Behaviors.GetProperty<DateTime>(CacheProperties.LastCachedTimeUtc);

            return lastModifiedUtc;
        }

        private bool isExpired(CofeSystemInfo entry, DateTime? lastModifiedUtc, DateTimeOffset? ifModifiedSince)
        {
            if (lastModifiedUtc.HasValue && Request.Headers.IfModifiedSince.HasValue)
            {
                if (entry is Cofe.Core.EntryType.EntryTypeInfo)
                    return false;

                if (lastModifiedUtc.Value.Subtract(Request.Headers.IfModifiedSince.Value.UtcDateTime).TotalSeconds < 10)
                    return false;
            }
            return true;
        }

        private ResponseFormatterInfo createResponseFormatterInfo(WebResourceAttribute webResourceAttrib, DateTime? lastModifiedUtc)
        {
            var retVal = new ResponseFormatterInfo()
            {
                StatusCode = HttpStatusCode.OK,
                ResourceAttribute = webResourceAttrib
            };

            switch (webResourceAttrib.CacheMethod)
            {
                case WebCacheMethod.LastModified:
                    retVal.LastModified = lastModifiedUtc;
                    break;
                case WebCacheMethod.MaxAge:
                    retVal.MaxAge = TimeSpan.FromHours(0.5);
                    break;
            }

            return retVal;
        }

        #endregion

        //Entry related.

        #region [C] AddEntry - PUT api/entry/{id}

        [HttpPut]
        [Authorize]
        public async Task<HttpResponseMessage> AddEntry(Guid id, HttpRequestMessage req)
        {
            #region Parse Entry
            var relativeDir = (await EntryFE.TryParsePathAsync<ICofeDirectoryInfo>(id.ToString(), ParsableType.Guid) as CofeDirectoryInfo);
            if (relativeDir == null)
                return new HttpResponseMessage(HttpStatusCode.NotFound);
            #endregion

            IEntrySerializerHelper[] helpers =
                new IEntrySerializerHelper[] { new CreateUsingNameAndType(relativeDir), EntrySerializerHelpers.UpdateToCache };

            var entry = CofeWsServices.HttpMessageFormatter.CreateRequestObject<CofeSystemInfo>(req,
                new ParameterDic() { EntrySerializerHelpers = helpers });
            
            if (entry == null || entry.Id == Guid.Empty)
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);

            return CofeWsServices.HttpMessageFormatter.CreateResponse(Request, entry, null, new ResponseFormatterInfo()
            {
                StatusCode = HttpStatusCode.OK
            });
        }

        #endregion

        #region [R] GetEntry - GET api/entry/{id}

        [HttpGet]
        public async Task<HttpResponseMessage> GetEntry(Guid id, string rel = null)
        {
            var entry = (await EntryFE.TryParsePathAsync(id.ToString(), ParsableType.Guid) as CofeSystemInfo);
            if (entry == null)
                return Request.CreateResponse(HttpStatusCode.NotFound);

            DateTime? lastModifiedUtc = getLastModifiedTime(entry, false);
            if (!(isExpired(entry, lastModifiedUtc, Request.Headers.IfModifiedSince)))
                return new HttpResponseMessage(HttpStatusCode.NotModified);

            if (rel != null)
                return handleRedirect(entry, rel);

            return CofeWsServices.HttpMessageFormatter.CreateResponse(Request, entry, null, new ResponseFormatterInfo()
            {
                StatusCode = HttpStatusCode.OK,
                LastModified = lastModifiedUtc
            });
        }
        #endregion

        #region [U] UpdateEntry - POST api/entry/{id} or POST api/entry

        [HttpPost]
        [Authorize]
        public async Task<HttpResponseMessage> UpdateEntry(HttpRequestMessage req)
        {
            var entry = CofeWsServices.HttpMessageFormatter.CreateRequestObject<CofeSystemInfo>(req);

            if (entry == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            await entry.Properties.Behaviors.CommitChangesAsync();

            return CofeWsServices.HttpMessageFormatter.CreateResponse(Request, entry, null, new ResponseFormatterInfo()
            {
                StatusCode = HttpStatusCode.OK
            });
        }

        [HttpPost]
        [Authorize]
        public async Task<HttpResponseMessage> UpdateEntry(HttpRequestMessage req, Guid id)
        {
            IEntrySerializerHelper[] helpers =
                new IEntrySerializerHelper[] { new FindSpecifyingId(id), EntrySerializerHelpers.UpdateToCache };
            var entry = CofeWsServices.HttpMessageFormatter.CreateRequestObject<CofeSystemInfo>(req,
                new ParameterDic() { EntrySerializerHelpers = helpers });

            if (entry == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            await entry.Properties.Behaviors.CommitChangesAsync();

            return CofeWsServices.HttpMessageFormatter.CreateResponse(Request, entry, null, new ResponseFormatterInfo()
            {
                StatusCode = HttpStatusCode.OK
            });
        }

        #endregion

        #region [D] DeleteEntry - DELETE api/entry/{id}

        [HttpDelete]
        [Authorize]
        public async Task<HttpResponseMessage> DeleteEntry(Guid id)
        {
            var entry = (await EntryFE.TryParsePathAsync(id.ToString(), ParsableType.Guid) as CofeSystemInfo);

            if (entry == null)
                return new HttpResponseMessage(HttpStatusCode.NotFound);

            await entry.DeleteAsync();

            return new HttpResponseMessage(HttpStatusCode.OK);
        }

        #endregion

        //Entry resource related.

        #region [R] GetResource - GET api/entry/{id}/{resource}

        [HttpGet]
        [Authorize]
        public async Task<HttpResponseMessage> GetResource(Guid id, string resource)
        {

            #region Parse Entry and acquire lastModifiedTime, Determine whether expired

            var entry = (await EntryFE.TryParsePathAsync(id.ToString(), ParsableType.Guid) as CofeSystemInfo);

        
            #endregion

            #region Debug

#if DEBUG
            //if (lastModifiedUtc.HasValue && lastModifiedUtc.Value <= CofeConstants.MinDateTime)
            //    if (id != Guid.Empty) //Root
            //      if (Debugger.IsAttached)
            //          Debugger.Break();
#endif
            #endregion

            #region Throw error if entry / resource not found / not supported / not a web resource

            if (entry == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            object property = CofeServices.PropertyDictionary.GetProperty(resource);
            if (property == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            DateTime? lastModifiedUtc = getLastModifiedTime(entry, property.Equals(CofeDirectoryProperties.List));
            if (!(isExpired(entry, lastModifiedUtc, Request.Headers.IfModifiedSince)))
                return new HttpResponseMessage(HttpStatusCode.NotModified);


            if (!(entry.Properties.Behaviors.IsPropertySupported(property)))
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            if (!(DecoratorExtension.IsWebResource(property)))
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            #endregion

            #region Generate Result

            object retVal;
            ParameterDic pd = null;

            try
            {
                if (DecoratorExtension.IsAction(property))
                {
                    pd = ControllerUtils.getParamDicFromQueryString(property);
                    Type type;
                    entry.Properties.Behaviors.InvokeNonGeneric(property, pd, out type, out retVal);
                }
                else retVal = entry.Properties.Behaviors.GetProperty(property).Value;
            }
            catch (Exception ex)
            {
                string errorStr = String.Format("Exception thrown when obtaining resource, entry={0}, resource={1}", id, resource);
                CofeServices.Logger.Log(new Exception(errorStr, ex));
                throw new HttpResponseException(HttpStatusCode.InternalServerError);
            }

            if (pd != null)
                pd.AddOrUpdate(ParameterKeys.CurrentUri, System.Web.HttpContext.Current.Request.Url);

            #endregion

            #region Output Result

            var webResourceAttrib = AttributeUtils<WebResourceAttribute>.FindAttribute(property);
            return CofeWsServices.HttpMessageFormatter.CreateResponse(Request, retVal, pd,
                createResponseFormatterInfo(webResourceAttrib, lastModifiedUtc));

            #endregion
        }

        #endregion

        #region [E/U] InvokeAction / PostResource(NotWorking) - POST api/entry/{id}/{action/resource}

        public static async Task<HttpResponseMessage> invokeUpload(HttpRequestMessage req, ICofeDirectoryInfo rootDirectory)
        {
            //http://stackoverflow.com/questions/10320232/how-to-accept-a-file-post-asp-net-mvc-4-webapi/10327789#10327789
            //http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2
            if (rootDirectory != null && req.Content.IsMimeMultipartContent())
            {
                var provider = await (req.Content.ReadAsMultipartAsync());
                List<CofeSystemInfo> retList = new List<CofeSystemInfo>() ;

                foreach (var content in provider.Contents)
                {
                    string fileName = content.Headers.ContentDisposition.FileName.Trim('\"');
                    var file = (await rootDirectory.CreateAsync("file", fileName)).As<Cofe.IO.IFileInfo>();
                    using (var inputStream = await content.ReadAsStreamAsync())
                    using (var outputStream = await file.OpenWriteAsync())
                        StreamUtils.CopyStream(inputStream, outputStream.Stream);
                    file.Properties.Behaviors.Invoke(DatabaseProperties.WriteRepositoryAsync,
                        new ParameterDic() { UpdateCurrentEntry = true, UpdateSubEntries = false });
                    var entry = await EntryFE.TryParsePathAsync(file.Id.ToString(), ParsableType.Guid) as CofeSystemInfo;
                    retList.Add(entry);
                }

                return CofeWsServices.HttpMessageFormatter.CreateResponse(req, retList, null, new ResponseFormatterInfo()
                {
                    StatusCode = HttpStatusCode.OK
                });
            }
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        }

        public static async Task<HttpResponseMessage> invokeAction(HttpRequestMessage req, Guid id, string resource)
        {
            string action = resource;
            #region Parse Entry
            var entry = (await EntryFE.TryParsePathAsync(id.ToString(), ParsableType.Guid) as CofeSystemInfo);

            #endregion

            #region Throw error if entry / action not found / not supported / not an action or web action

            if (entry == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            if (String.Equals(resource, "upload", StringComparison.CurrentCultureIgnoreCase))
                return await invokeUpload(req, entry.As<ICofeDirectoryInfo>());

            object property = CofeServices.PropertyDictionary.GetProperty(action);
            if (property == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            if (!(entry.Properties.Behaviors.IsPropertySupported(property)))
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            #endregion

            if (DecoratorExtension.IsWebAction(property) && DecoratorExtension.IsAction(property))
            {

                #region Generate Result

                object retVal;
                ParameterDic pd = ControllerUtils.getParamDicFromQueryString(property);

                try
                {
                    Type type;
                    entry.Properties.Behaviors.InvokeNonGeneric(property, pd, out type, out retVal);
                    if (retVal == null)
                        retVal = entry;
                }
                catch (Exception ex)
                {
                    string errorStr = String.Format("Exception thrown when running action, entry={0}, action={1}", id, action);
                    CofeServices.Logger.Log(new Exception(errorStr, ex));
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);
                }

                if (pd != null)
                    pd.AddOrUpdate(ParameterKeys.CurrentUri, System.Web.HttpContext.Current.Request.Url);

                #endregion

                #region Output Result
                if (retVal != null)
                {
                    var webResourceAttrib = AttributeUtils<WebResourceAttribute>.FindAttribute(property);
                    return CofeWsServices.HttpMessageFormatter.CreateResponse(req, retVal, pd, new ResponseFormatterInfo()
                    {
                        StatusCode = HttpStatusCode.OK,
                        ResourceAttribute = webResourceAttrib
                    });
                }
                else return new HttpResponseMessage(HttpStatusCode.OK);
                #endregion
            }
            else if (DecoratorExtension.IsWebResource(property))
            {
                var type = CofeServices.PropertyDictionary.AttributeHelper.GetDefaultType(property);
                var value = CofeWsServices.HttpMessageFormatter.CreateRequestObject(req, type);
                if (value != null)
                    await entry.Properties.Behaviors.SetPropertyAsync(PropertyPair.FromValue(property, value));
                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            else throw new HttpResponseException(HttpStatusCode.BadRequest);
        }

        [HttpPost]
        [Authorize]
        public async Task<HttpResponseMessage> InvokeAction(HttpRequestMessage req, Guid id, string resource)
        {
            return await EntryController.invokeAction(req, id, resource);
        }

        #endregion

    }
}
